﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
using System.IO;
using System.Windows.Media;
using ThreeByte.Media;
using System.Security.Cryptography;

namespace ThreeByte.ActiveDeck.Config
{
    public enum ImageType { PNG, JPEG };

    public class GraphicResource
    {
        private static readonly XNamespace ns = Service.ServiceConfiguration.NAMESPACE;
        public static readonly XName XML_NAME = ns + "Graphic";

        public Guid ID {
            get;
            protected set;
        }

        public string Name {
            get;
            protected set;
        }

        public BitmapSource Image {
            get {
                using(MemoryStream ms = new MemoryStream(GraphicBytes)) {
                    BitmapImage newImage = new BitmapImage();
                    newImage.BeginInit();
                    newImage.CacheOption = BitmapCacheOption.OnLoad;
                    newImage.StreamSource = ms;
                    newImage.EndInit();
                    newImage.Freeze();
                    return newImage;
                }
            }
        }

        public ImageType ImageType {
            get;
            protected set;
        }

        public virtual byte[] GraphicBytes {
            get;
            protected set;
        }

        public string Hash {
            get;
            protected set;
        }

        #region Constructors
        protected GraphicResource() {
            ID = Guid.NewGuid();
            ImageType = ImageType.PNG;
        }

        public GraphicResource(string name, Uri imageUri, ImageType imageType = ImageType.PNG) : this() {
            Name = name;

            BitmapImage newImage = new BitmapImage();
            newImage.BeginInit();
            newImage.CacheOption = BitmapCacheOption.OnLoad;
            newImage.UriSource = imageUri;
            newImage.EndInit();
            newImage.Freeze();

            ImageType = imageType;
            GraphicBytes = GetGraphicBytes(newImage, imageType);
            Hash = GenerateHash(GraphicBytes);
        }

        public GraphicResource(BitmapSource image, string name = null, ImageType imageType = ImageType.PNG) : this() {
            Name = name;
            ImageType = imageType;
            GraphicBytes = GetGraphicBytes(image, ImageType);
            Hash = GenerateHash(GraphicBytes);
        }

        private GraphicResource(Guid id, string name, string imageString, ImageType imageType = ImageType.PNG) : this() {
            ID = id;
            Name = name;
            ImageType = imageType;
            GraphicBytes = GetGraphicBytes(ImageUtil.DecodeBitmapImage(imageString), ImageType);
            Hash = GenerateHash(GraphicBytes);
        }

        public GraphicResource(Guid id, string name, BitmapSource image) : this() {
            ID = id;
            Name = name;
            GraphicBytes = GetGraphicBytes(image, ImageType);
            Hash = GenerateHash(GraphicBytes);
        }

        #endregion Constructors

        public XElement ToXml() {

            XElement node = GetXmlHeader();
            //Serialize the image
            BitmapEncoder encoder = new PngBitmapEncoder();
            if(ImageType == ImageType.JPEG) {
                encoder = new JpegBitmapEncoder();
            }
            node.Value = ImageUtil.EncodeBitmapImage(Image, encoder);
            return node;
        }

        public XElement GetXmlHeader() {
            XNamespace ns = Service.ServiceConfiguration.NAMESPACE;
            XElement node = new XElement(XML_NAME, new XAttribute("ID", ID.ToString()), new XAttribute("Name", Name ?? string.Empty));
            node.Add(new XAttribute("MD5", Hash));
            if(ImageType == ImageType.PNG) {
                node.Add(new XAttribute("Type", "PNG"));
            } else {
                node.Add(new XAttribute("Type", "JPEG"));
            }
            //Calculate the size
            node.Add(new XAttribute("Size", GraphicBytes.Length));

            return node;
        }

        private static byte[] GetGraphicBytes(BitmapSource image, ImageType imageType) {
            FormatConvertedBitmap formattedIcon = new FormatConvertedBitmap();
            formattedIcon.BeginInit();
            formattedIcon.Source = image;
            formattedIcon.DestinationFormat = PixelFormats.Bgra32;//.Rgb24;
            formattedIcon.EndInit();
            BitmapFrame frame = BitmapFrame.Create(formattedIcon);

            //Write this to a PNG byte array for the database
            BitmapEncoder encoder = new PngBitmapEncoder();
            if(imageType == ImageType.JPEG) {
                encoder = new JpegBitmapEncoder();
            }
            encoder.Frames.Add(frame);
            MemoryStream mem = new MemoryStream();
            encoder.Save(mem);

            byte[] graphicBytes = mem.GetBuffer();
            mem.Dispose();
            return graphicBytes;
        }

        public static GraphicResource FromXml(XElement node) {
            Guid id = Guid.Parse(node.Attribute("ID").Value);
            string name = node.Attribute("Name").Value;
            string encodedImageString = node.Value;
            ImageType type = ImageType.PNG;
            if(node.Attribute("Type") != null && node.Attribute("Type").Value == "JPEG") {
                type = ImageType.JPEG;
            }
            //string hash = null;
            //if(node.Attribute("MD5") != null) {
            //    hash = node.Attribute("MD5").Value;
            //}

            return new GraphicResource(id, name, encodedImageString, type);
        }

        public bool IsByteEquivalent(GraphicResource other) {
            return GraphicBytes.SequenceEqual(other.GraphicBytes);
        }

        protected static string GenerateHash(byte[] graphicBytes){
            MD5 md5Hash = MD5.Create();
            byte[] data = md5Hash.ComputeHash(graphicBytes);
            StringBuilder sb = new StringBuilder();
            foreach(byte b in data) {
                sb.AppendFormat("{0:X2}", b);
            }
            return sb.ToString();
        }


        //TODO: Check last updated time?
        public bool Matches(XElement resourceInfo) {
            try {
                //Verify ID
                if(ID != Guid.Parse(resourceInfo.Attribute("ID").Value)) { return false; }

                //Size
                if(GraphicBytes.Length != int.Parse(resourceInfo.Attribute("Size").Value)) { return false; }

                //Type
                if(ImageType != ParseImageType(resourceInfo.Attribute("Type").Value)) { return false; }

                //All tests pass
                return true;
            } catch {
                //Doesn't matter, just false
            }
            return false;
        }

        private static ImageType ParseImageType(string imageType) {
            if(imageType.ToLower() == "png") {
                return ImageType.PNG;
            } else if(imageType.ToLower() == "jpeg") {
                return ImageType.JPEG;
            }

            throw new ArgumentOutOfRangeException("imageType");
        }
    }
}
